/*
 * Kickme chalange for Newbie Contest
 * by RaX` <raxxx.fr@gmail.com>
 */

#include <iostream>
#include <sstream>
#include <string>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <bignum/bignum.h>
#include <md5.h>

#define SERV_ADDR "195.225.204.21"
#define SERV_PORT 6667

#define MAX_FIB 2500

class OpenSocket
{

public:

  OpenSocket (char*, int);
  virtual ~OpenSocket ();


  std::string get_message (void);
  void send_message (char*);

private:

  int m_socket;
  struct sockaddr_in irc_sock;

};

std::string fibo (int);
std::string md5sum (char *);

int
main (void)
{

  OpenSocket::OpenSocket sock (SERV_ADDR, SERV_PORT);

  std::stringstream channel;

  for (;;)
   {

    std::string recv;
    recv = sock.get_message ();

    if (recv.rfind ("*** No Ident response") != std::string::npos)
     {
      sock.send_message ("USER Guest luna * RaX\n");
      sock.send_message ("NICK raxou\n");
      sock.send_message ("PRIVMSG Daneel :.challenge_kickme start\n");
      sleep (1);
      sock.send_message ("WHOIS Daneel\n");
     }
    else if (recv.rfind ("Daneel :@#KM_") != std::string::npos)
     {
      int i = recv.rfind ("Daneel :@#KM_") + 13;
      int y = recv.rfind ("+#nc-irc-challs") - 1;

      std::stringstream response;

      channel << "#KM_"
              << recv.substr (i, y - i);

      response << "JOIN "
               << channel.str ()
               << std::endl;

      std::cout << "\e[1;31m" << response.str () << "\e[0m";
      sock.send_message ((char*)response.str ().c_str ());
     }
    else if (recv.rfind ("Quel est le hash md5 du ") != std::string::npos)
     {
      std::stringstream response;

      int i = recv.rfind ("Quel est le hash md5 du ") + 24;
      int y = recv.rfind ("ème");

      int n = atoi (recv.substr (i, y - i).c_str ());

      response << "PRIVMSG Daneel :.challenge_kickme "
               << md5sum ((char*)fibo (n).c_str ())
               << std::endl;

      std::cout << "\e[1;31m" << response.str () << "\e[0m";
      sock.send_message ((char*)response.str ().c_str ());
     }
    else if (recv.rfind ("+o raxou") != std::string::npos)
     {
      std::stringstream response;
      
      response << "KICK "
               << channel.str ()
               << " Daneel"
               << std::endl;

      std::cout << "\e[1;31m" << response.str () << "\e[0m";
      sock.send_message ((char*)response.str ().c_str ());
     }

    if (!recv.empty ())
      std::cout << recv << std::endl;

    if (recv.rfind ("Le mot de passe pour valider") != std::string::npos)
      break;

    recv.clear ();

   }

  return EXIT_SUCCESS;

}

OpenSocket::OpenSocket (char *ip_addr, int port)
{

  struct sockaddr_in irc_sock;

  irc_sock.sin_addr.s_addr = inet_addr (ip_addr);
  irc_sock.sin_family = AF_INET;
  irc_sock.sin_port = htons (port);

  m_socket = socket (AF_INET, SOCK_STREAM, 0);
  if (m_socket == 0)
   {
     std::cerr << "Unable to create socket" << std::endl;
     exit (EXIT_FAILURE);
   }

  if (connect (m_socket, (const sockaddr*) &irc_sock, sizeof (irc_sock)) == -1)
   {
    std::cerr << "Failed to connect" << std::endl;
    exit (EXIT_FAILURE);
   }

}

std::string
OpenSocket::get_message (void)
{

  char rcv_buffer[512];

  memset (&rcv_buffer, 0, sizeof rcv_buffer);
  read (m_socket, &rcv_buffer, sizeof (rcv_buffer));


  return std::string (rcv_buffer);

}

void
OpenSocket::send_message (char* message)
{

  write (m_socket, message, strlen (message));

}

OpenSocket::~OpenSocket (void)
{

  close (m_socket);

}

std::string
fibo (int n)
{

  if (n > MAX_FIB - 1)
    return std::string ("error");

  bignum fib[MAX_FIB];
  std::stringstream stream;
  int i;

  for (i = 0; i < MAX_FIB; i++)
   {
    if (i == 0)
      fib[i] = 0;
    else if (i == 1)
      fib[i] = 1;
    else
      fib[i] = fib[i-1] + fib[i-2];
      if (i == n)
        stream << fib[i];
   }

  return stream.str ();

}

std::string
md5sum (char *str)
{

	md5_state_t state;
	md5_byte_t digest[16];
  std::string md5;

  md5.clear ();

	md5_init (&state);
	md5_append (&state, (const md5_byte_t*) str, strlen (str));
	md5_finish (&state, digest);

  for (unsigned int i = 0; i < sizeof digest; i++)
   {
    char tmp[3];
    sprintf ((char*)&tmp, "%02x", digest[i]);    
    md5 += tmp;
   }

  return md5;

}

